package org.example.project.common.error.handle;

import lombok.extern.slf4j.Slf4j;
import org.example.project.common.error.MessageEnum;
import org.example.project.common.error.enums.ErrorCode;
import org.example.project.common.error.enums.ErrorCodeMapper;
import org.example.project.common.error.exception.BizCustomException;
import org.example.project.common.error.exception.DaoException;
import org.example.project.common.error.exception.ServiceException;
import org.example.project.common.contant.Log;
import org.example.project.common.contant.R;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.util.WebUtils;

/**
 * 全局异常处理
 * <p>
 * 1. 处理web框架类异常、自定义异常
 * 2. 返回A类错误码
 * ×    2.1 对于B、C类错误，直接返回
 * ×    2.2 记录日志，封装A类错误码返回
 *
 * @author wenxy
 * @date 2020/10/26
 */
@RestControllerAdvice
@Slf4j
public class DefaultGlobalErrorHandle extends ResponseEntityExceptionHandler {

    /**
     * 兼容自定义业务异常
     *
     * @param bizCustomException bizCustomException
     * @return ResponseEntity<R>
     */
    @ExceptionHandler(value = {BizCustomException.class})
    public ResponseEntity<R<String>> handleException(BizCustomException bizCustomException) {
        // 自定义业务异常在ServiceLogAspect已经处理，这里冗余简单记录，处理controller抛出的异常
        ServiceException serviceException = new ServiceException(bizCustomException.getMessageEnum(), bizCustomException.getMessage());
        return handleException(serviceException);
    }

    /**
     * 第三方调用C类异常-服务端-第三方错误、第二类B类异常-服务端错误
     *
     * @param daoException daoException
     * @return ResponseEntity<R < String>>
     */
    @ExceptionHandler(value = {DaoException.class})
    public ResponseEntity<R<String>> handleException(DaoException daoException) {
        ServiceException serviceException = new ServiceException(daoException.getMessageEnum(), daoException.getMessage());
        return handleException(serviceException);
    }

    /**
     * 第三方调用C类异常-服务端-第三方错误、第二类B类异常-服务端错误
     *
     * @param serviceException
     * @return
     */
    @ExceptionHandler(value = {ServiceException.class})
    public ResponseEntity<R<String>> handleException(ServiceException serviceException) {
        // B/C类异常在DaoLogAspect/ServiceLogAspect已经处理并打印堆栈，这里冗余简单记录，防止手动异常错误使用，如在Service/controller抛出daoException，在controller抛出service不被处理
        logWarnSimple(serviceException);

        MessageEnum messageEnum = serviceException.getMessageEnum();
        if (messageEnum instanceof ErrorCode) {
            return ResponseEntity.status(ErrorCodeMapper.toHttpStatus((ErrorCode) messageEnum))
                    .body(R.fail(serviceException));
        }

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(R.fail(serviceException));
    }


    /**
     * A类异常-客户端错误
     *
     * @param throwable throwable
     * @return
     */
    @ExceptionHandler(value = {Throwable.class})
    public ResponseEntity<R> handleThrowable(Throwable throwable) {
        // A类异常简单记录信息，不用记录堆栈，堆栈一定是web框架的错误
        logWarnSimple(throwable);

        // TODO 处理常见的web框架类错误
        // 过滤器、请求转换、参数校验、拦截器
        return ResponseEntity.badRequest().body(R.fail(ErrorCode.A0001, throwable.getMessage()));
    }


    private void logWarnSimple(Throwable exception) {
        if (log.isWarnEnabled()) {
            log.warn(Log.simpleLogBuilder().result(exception.getMessage()).build().toString());
        }
    }

    /**
     * 覆盖ResponseEntityExceptionHandler，返回统一的异常响应
     *
     * @param ex
     * @param body
     * @param headers
     * @param status
     * @param request
     * @return
     */
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        }
        return ResponseEntity.status(status).body(R.fail(toErrorCode(status), ex.getMessage()));
    }

    private MessageEnum toErrorCode(HttpStatus httpStatus) {
        return new MessageEnum() {
            @Override
            public String code() {
                return String.valueOf(httpStatus.value());
            }

            @Override
            public String msg() {
                return httpStatus.getReasonPhrase();
            }
        };
    }
}
